Building Our Way to Affordability: A YIMBY Policy Analysis

Author

Shreya Karki

Published

October 24, 2025

1 Introduction

Housing affordability is one of America’s most pressing urban challenges. This analysis identifies the nation’s most pro-housing metropolitan areas by examining construction patterns, economic opportunity, and housing costs across Core-Based Statistical Areas.

Following the YIMBY idea that permissive zoning and increased housing supply can reduce costs and support more dynamic, inclusive communities, we assess which regions are building enough homes to meet demand and which are falling behind.

2 Data Relationship Diagram

TipKey Data Relationships
  • All ACS tables (INCOME, RENT, POPULATION, HOUSEHOLDS) share the composite key GEOID + year.
  • PERMITS uses CBSA, which corresponds to ACS GEOID, joins on geography + year.
  • WAGES uses BLS industry codes (e.g., 101, 1011) that approximate but do not exactly match NAICS in INDUSTRY_CODES; this link is conceptual.
  • WAGES connects to ACS and PERMITS after transforming CBSA → “C” + first 4 digits (e.g., 10180 → C1018).
  • For clarity, not every possible line is drawn — the diagram shows only the primary joins used in analysis.

This ERD was created in dbdiagram.io and embedded as an SVG for best visual clarity.

3 Construction Leadership & Housing Supply Analysis (Data Exploration)

3.1 National Housing Production Leaders: 2010-2019

Code
# Identify metropolitan areas with highest housing production
top_housing_metros <- inner_join(PERMITS, INCOME, join_by(CBSA == GEOID), 
           suffix = c("_permits", "_income")) |>
  filter(year_permits >= 2010, year_permits <= 2019) |>
  group_by(NAME) |>
  summarise(total_units = sum(new_housing_units_permitted, na.rm = TRUE)) |>
  arrange(desc(total_units)) |>
  slice_max(total_units, n = 1) |>
  ungroup()

The Dallas-Fort Worth-Arlington, TX Metro Area metropolitan area permitted 6,451,564 new housing units from 2010-2019, demonstrating the scale achievable under pro-housing policies. This level of production represents a benchmark for regional housing growth targets.

3.2 Market Volatility Analysis: Albuquerque Case Study

Table: Annual Housing Production — Albuquerque Metro Area
Code
library(DT)

# Analyze construction patterns in a mid-sized metro
albuquerque_construction <- PERMITS |>
  filter(CBSA == 10740) |>
  group_by(year) |>
  summarise(`Housing Units Permitted` = sum(new_housing_units_permitted, na.rm = TRUE)) |>
  arrange(year)

datatable(
  albuquerque_construction,
  caption = 'Annual Housing Production: Albuquerque Metropolitan Area',
  options = list(pageLength = 10, searching = FALSE)
)

Albuquerque’s housing permits peaked in 2021, with the highest pre-pandemic level recorded in 2013.

Interpretation: This pattern shows a brief spike rather than a steady climb. To judge local homebuilding capacity, it is better to look at multi-year trajectories than any single high point.

3.3 Regional Income Distribution Patterns

Code
# filter year 
income_2015 <- INCOME |> 
  filter(year == 2015)

# inner-join tables and create a new column 
income_2015 <- income_2015 |>
  inner_join(HOUSEHOLDS, by = c("GEOID", "year")) |>
  inner_join(POPULATION, by = c("GEOID", "year")) |>
  select(GEOID, NAME, year, household_income, households, population) |>
  mutate(total_income_cbsa = household_income * households,
         state = str_extract(NAME, ", (.{2})", group = 1))

# state with highest average individual income
top_state_income <- income_2015 |>
  group_by(state) |>
  summarise(total_income_state = sum(total_income_cbsa, na.rm = TRUE),
            total_population_state = sum(population, na.rm = TRUE)) |>
  mutate(avg_individual_income = total_income_state / total_population_state) |>
  arrange(desc(avg_individual_income)) |>
  slice_max(avg_individual_income, n = 1)

In DC, the average individual income was the highest in 2015 at approximately $33,233 per person.

Interpretation: High income alone does not ensure affordability. Without enough housing, rents rise faster than wages.

3.4 High-Skill Employment Distribution

Code
# Filter WAGES for data scientists (NAICS 5182)
Data_scientist <- WAGES |>
  filter(INDUSTRY == 5182) |>
  select(YEAR, FIPS, EMPLOYMENT, AVG_WAGE) |>
  mutate(std_cbsa = paste0(FIPS, "0"))

# Prepare POPULATION table for joining (to get CBSA names)
POP_join <- POPULATION |>
  mutate(std_cbsa = paste0("C", GEOID)) |>
  select(std_cbsa, NAME, year)

# Join to find which CBSA had the most data scientists each year
ds_named <- Data_scientist |>
  inner_join(POP_join, by = join_by(std_cbsa, YEAR == year)) |>
  group_by(YEAR, NAME) |>
  summarise(EMPLOYMENT = sum(EMPLOYMENT, na.rm = TRUE)) |>
  slice_max(EMPLOYMENT, n = 1) |>
  arrange(YEAR)

In the latest year (2023), San Francisco-Oakland-Fremont, CA Metro Area has the most data scientists. New York–Newark–Jersey City last held the top spot in 2015.

Interpretation: Fast growth in high-paying tech jobs raises housing demand. Cities that permit new homes steadily can prevent rent spikes as their job markets expand.

3.5 Financial Sector Concentration: NYC Case Study

In the New York City CBSA, workers employed in the Finance and Insurance industries (NAICS 52) earned about 4.6% of all wages.

This share peaked in 2014, showing how important finance remains to NYC’s economy.

Interpretation: Finance jobs bring high wages, but without new housing construction, those wage gains mostly raise rents instead of improving overall affordability.

5 Rent Burden: Understanding Housing Affordability

5.0.1 Defining the Metric

Rent burden measures the share of household income spent on rent — a direct indicator of housing affordability.
The INCOME and RENT tables are joined by geography and year to compute:

Rent Burden % = (Monthly Rent × 12) / Household Income × 100

To make comparisons meaningful across regions and years, the metric is standardized so that 0 = lowest and 100 = highest rent burden observed in the study period.

Code
# Standardize rent burden metric
rent_burden_data <- INCOME |>
  inner_join(RENT, by = c("GEOID", "year")) |>
  select(-NAME.y) |>
  rename(City_Name = NAME.x) |>
  mutate(
    yearly_rent = monthly_rent * 12,
    rent_income_ratio = yearly_rent / household_income,
    rent_burden_percent = rent_income_ratio * 100
  )

lowest_burden <- min(rent_burden_data$rent_burden_percent, na.rm = TRUE)
highest_burden <- max(rent_burden_data$rent_burden_percent, na.rm = TRUE)

rent_burden_data <- rent_burden_data |>
  mutate(
    burden_score = 100 * (rent_burden_percent - lowest_burden) /
                     (highest_burden - lowest_burden)
  )

5.2 Priority Intervention Regions

Table: Highest Rent Burden
Code
latest_year <- max(rent_burden_data$year, na.rm = TRUE)

most_burdened <- rent_burden_data |>
  filter(year == latest_year) |>
  select(
    City = City_Name,
    Year = year,
    `Monthly Rent` = monthly_rent,
    `Household Income` = household_income,
    `Rent Burden %` = rent_burden_percent,
    `Burden Score` = burden_score
  ) |>
  mutate(
    `Monthly Rent` = round(`Monthly Rent`, 0),
    `Household Income` = round(`Household Income`, 0),
    `Rent Burden %` = round(`Rent Burden %`, 1),
    `Burden Score` = round(`Burden Score`, 1)
  ) |>
  arrange(desc(`Burden Score`)) |>
  head(5)

datatable(
  most_burdened,
  caption = paste("Cities with Highest Rent Burden -", latest_year),
  options = list(searching = FALSE)
)
Code
# Calculate values for policy insight
top_city <- most_burdened$City[1]
top_burden <- most_burdened$`Rent Burden %`[1]
top_score <- most_burdened$`Burden Score`[1]

Interpretation: Clearlake, CA Micro Area faces the highest rent burden in the latest year, with residents spending about 31.2% of income on rent (standardized score 72.9).

This indicates acute affordability pressure, consistent with demand outpacing new supply. These metros are strong candidates for targeted permitting reforms and federal YIMBY incentives to expand rental stock quickly.

5.3 Affordable Housing Models

Table: Lowest Rent Burden
Code
least_burdened <- rent_burden_data |>
  filter(year == latest_year) |>
  select(
    City = City_Name,
    Year = year,
    `Monthly Rent` = monthly_rent,
    `Household Income` = household_income,
    `Rent Burden %` = rent_burden_percent,
    `Burden Score` = burden_score
  ) |>
  mutate(
    `Monthly Rent` = round(`Monthly Rent`, 0),
    `Household Income` = round(`Household Income`, 0),
    `Rent Burden %` = round(`Rent Burden %`, 1),
    `Burden Score` = round(`Burden Score`, 1)
  ) |>
  arrange(`Burden Score`) |>
  head(5)

datatable(
  least_burdened,
  caption = paste("Cities with Lowest Rent Burden -", latest_year),
  options = list(searching = FALSE)
)
Code
# Calculate values for policy insight
bottom_city <- least_burdened$City[1]
bottom_burden <- least_burdened$`Rent Burden %`[1]
bottom_score <- least_burdened$`Burden Score`[1]

Interpretation: Laconia, NH Micro Area indicates the lowest rent burden in the latest year, with residents spending about 12.7% of income on rent and a standardized score of 1.6.

This outcome is consistent with ample new supply and/or strong income growth, suggesting that steady permitting and streamlined approvals can keep housing costs in check.

6 Housing Growth: Measuring Supply Responsiveness

Housing growth captures how effectively cities add homes to match demand.

Using permit and population data, two complementary indicators were developed:
- an instantaneous production metric, measuring new housing relative to population size; and
- a rate-based growth metric, comparing permitting to five-year population change.

Combining these yields a composite housing growth score, which highlights metros that are both building actively and scaling with demographic trends.

Code
# Calculate housing growth metrics using population and permits data
library(dplyr)

housing_growth_data <- POPULATION |>
  inner_join(PERMITS, by = c("GEOID" = "CBSA", "year")) |>
  arrange(NAME, year) |>
  group_by(NAME) |>
  mutate(
    pop_5yr_growth = (population - lag(population, 5)) / lag(population, 5) * 100,
    permits_per_1000 = (new_housing_units_permitted / population) * 1000,
    permits_vs_growth = ifelse(pop_5yr_growth > 0, 
                              new_housing_units_permitted / (population * pop_5yr_growth / 100), 
                              NA)
  ) |>
  ungroup() |>
  filter(year >= 2014)

housing_growth_data <- housing_growth_data |>
  mutate(
    instant_score = 100 * (permits_per_1000 - min(permits_per_1000, na.rm = TRUE)) / 
                   (max(permits_per_1000, na.rm = TRUE) - min(permits_per_1000, na.rm = TRUE)),
    rate_score = 100 * (permits_vs_growth - min(permits_vs_growth, na.rm = TRUE)) / 
                 (max(permits_vs_growth, na.rm = TRUE) - min(permits_vs_growth, na.rm = TRUE)),
    composite_score = (instant_score + rate_score) / 2
  )

6.1 Construction Hotspots: Where Building is Happening Now

Table: Construction Hotspots — Highest Current Production
Code
latest_year <- max(housing_growth_data$year, na.rm = TRUE)

instant_leaders <- housing_growth_data |>
  filter(year == latest_year) |>
  select(
    `Metro Area` = NAME,
    Year = year,
    Population = population,
    `New Permits` = new_housing_units_permitted,
    `Permits per 1000` = permits_per_1000,
    `Production Score` = instant_score
  ) |>
  mutate(
    `Permits per 1000` = round(`Permits per 1000`, 1),
    `Production Score` = round(`Production Score`, 1)
  ) |>
  arrange(desc(`Production Score`)) |>
  head(5)

datatable(
  instant_leaders,
  caption = paste("Construction Hotspots: Highest Current Housing Production -", latest_year),
  options = list(searching = FALSE)
)
Code
top_production <- instant_leaders$`Metro Area`[1]
top_production_score <- instant_leaders$`Production Score`[1]

Interpretation: Salisbury, MD Metro Area is currently the nation’s most active housing market, with a production score of 100.

This reflects strong near-term construction activity, signaling local policies that allow quick permitting and responsive homebuilding.

Such metros serve as models for short-run supply elasticity, showing that steady permitting can temper rent growth even in high-demand regions.

6.2 Growth-Responsive Housing Markets

Code
rate_leaders <- housing_growth_data |>
  filter(year == latest_year) |>
  select(
    `Metro Area` = NAME,
    Year = year,
    `5-Year Population Growth %` = pop_5yr_growth,
    `New Permits` = new_housing_units_permitted,
    `Growth Score` = rate_score
  ) |>
  mutate(
    `5-Year Population Growth %` = round(`5-Year Population Growth %`, 1),
    `Growth Score` = round(`Growth Score`, 1)
  ) |>
  arrange(desc(`Growth Score`)) |>
  head(5)

top_growth <- rate_leaders$`Metro Area`[1] 
top_growth_score <- rate_leaders$`Growth Score`[1]

Interpretation: Springfield, OH Metro Area demonstrates the most balanced housing response to population expansion, with a five-year growth-adjusted score of 7.7.

This means that new housing supply has kept pace with rapid population increases—an indicator of adaptive land-use policy and capacity planning that maintains affordability while accommodating in-migration.

Metros with low growth scores, by contrast, risk rising rent burdens as population outstrips new construction.

6.3 Overall Housing Growth

Code
composite_leaders <- housing_growth_data |>
  filter(year == latest_year) |>
  select(
    `Metro Area` = NAME,
    Year = year,
    `Production Score` = instant_score,
    `Growth Score` = rate_score,
    `Overall Score` = composite_score
  ) |>
  mutate(
    `Production Score` = round(`Production Score`, 1),
    `Growth Score` = round(`Growth Score`, 1),
    `Overall Score` = round(`Overall Score`, 1)
  ) |>
  arrange(desc(`Overall Score`)) |>
  head(5)

top_overall <- composite_leaders$`Metro Area`[1]
top_overall_score <- composite_leaders$`Overall Score`[1]

Interpretation: Punta Gorda, FL Metro Area leads the nation with an overall housing growth score of 28.6, balancing both strong construction volume and responsiveness to local population changes.

This composite result highlights how consistent permitting pipelines and pro-housing zoning frameworks can deliver stability across economic cycles.

Such metros illustrate the potential impact of federal-state collaboration on long-term housing affordability.

6.3.1 Policy Takeaway

Regions like Punta Gorda, FL Metro Area and Springfield, OH Metro Area show that consistent, multi-year housing production, most effectively keeps rent burdens stable.

Federal programs that reward steady permitting and year-over-year delivery can replicate this success across other metros.

7 Visualization

Code
# Join rent burden and housing growth datasets

yimby_base <- rent_burden_data |>
inner_join(housing_growth_data, by = c("City_Name" = "NAME", "year")) |>
select(City_Name, year, rent_burden_percent, burden_score,
population, pop_5yr_growth, composite_score)

# Calculate rent burden change, population growth, and housing growth
yimby_metrics <- yimby_base |>
group_by(City_Name) |>
mutate(
early_burden = mean(rent_burden_percent[year <= 2015], na.rm = TRUE),
recent_burden = mean(rent_burden_percent[year >= 2020], na.rm = TRUE),
burden_change = recent_burden - early_burden,
pop_growth = (max(population, na.rm = TRUE) - min(population, na.rm = TRUE)) /
min(population, na.rm = TRUE) * 100,
avg_housing_growth = mean(composite_score, na.rm = TRUE)
) |>
ungroup()

# Tag CBSAs based on the four rubric criteria

yimby_analysis <- yimby_metrics |>
mutate(
yimby_score = ifelse(
early_burden > median(early_burden, na.rm = TRUE) &
burden_change < 0 &
pop_growth > 0 &
avg_housing_growth > median(avg_housing_growth, na.rm = TRUE),
"YIMBY Success", "Other"
))

# Collapse to one row per CBSA for summary/plots

yimby_summary <- yimby_analysis |>
distinct(City_Name, .keep_all = TRUE)

# Four rubric criteria flags

yimby_flags <- yimby_summary |>
mutate(
high_early = early_burden > median(early_burden, na.rm = TRUE),
improved = burden_change < 0,
grew = pop_growth > 0,
above_avg_growth = avg_housing_growth > median(avg_housing_growth, na.rm = TRUE),
is_success_all4 = high_early & improved & grew & above_avg_growth
)

# Helpers for inline text
yimby_success_names <- yimby_flags |>
filter(is_success_all4) |>
arrange(burden_change) |>
pull(City_Name)

yimby_success_n <- length(yimby_success_names)
yimby_success_preview <- paste(head(yimby_success_names, 5), collapse = ", ")

7.1 Rent Burden vs Housing Growth

Code
library(ggplot2)

ggplot(yimby_summary,
aes(x = burden_change, y = avg_housing_growth, color = yimby_score)) +
geom_point(alpha = 0.6, size = 2) +
scale_color_manual(values = c("YIMBY Success" = "#2E8B57", "Other" = "gray70")) +
labs(
title = "Rent Burden Change vs Housing Growth",
subtitle = "Cities with strong housing growth tend to see rent burden improvements",
x = "Change in Rent Burden (percentage points)",
y = "Average Housing Growth Score",
color = "Metro Type"
) +
theme_minimal(base_size = 14) +
theme(
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(size = 11, color = "gray40"),
legend.position = "bottom",
panel.grid.minor = element_blank()
)

Cities shown in green (“YIMBY Success”) built more housing and saw rent burdens stabilize or decline over time.

This pattern supports the idea that steady housing production helps prevent rent spikes, even in growing metros.

7.2 Population Growth vs Housing Affordability

Code
ggplot(yimby_summary,
aes(x = pop_growth, y = -burden_change, color = yimby_score)) +
geom_point(alpha = 0.6, size = 2) +
scale_color_manual(values = c("YIMBY Success" = "#457B9D", "Other" = "gray70")) +
labs(
title = "Population Growth vs Rent Burden Improvement",
subtitle = "Higher population growth with lower rent burden indicates strong housing supply response",
x = "Population Growth (%)",
y = "Rent Burden Improvement (percentage points)",
color = "Metro Type"
) +
theme_minimal(base_size = 14) +
theme(
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(size = 11, color = "gray40"),
legend.position = "bottom",
panel.grid.minor = element_blank()
)

Many metros with strong population growth also achieved better affordability when new housing kept pace.

In contrast, regions with low construction saw rent burdens worsen — highlighting how growth without supply drives up costs.

7.3 Metros Achieving Affordability Through Construction

Table: Metropolitan Areas Improving Affordability Through Housing Supply (Top 10)
Code
yimby_success <- yimby_summary |>
  filter(yimby_score == "YIMBY Success") |>
  select(
    `Metro Area` = City_Name,
    `Early Rent Burden` = early_burden,
    `Rent Burden Change` = burden_change,
    `Population Growth %` = pop_growth,
    `Housing Growth Score` = avg_housing_growth
  ) |>
  mutate(
    `Early Rent Burden` = round(`Early Rent Burden`, 1),
    `Rent Burden Change` = round(`Rent Burden Change`, 1),
    `Population Growth %` = round(`Population Growth %`, 1),
    `Housing Growth Score` = round(`Housing Growth Score`, 1)
  ) |>
  arrange(desc(`Housing Growth Score`)) |>
  head(10)

DT::datatable(
  yimby_success,
  caption = "Metropolitan Areas Improving Affordability Through Housing Supply (Top 10)",
  options = list(pageLength = 10, searching = FALSE),
  rownames = FALSE
)

These metros represent the top 10 “YIMBY success stories” cities that started with relatively high rent burdens but achieved improvements over time while maintaining strong housing growth.

The leading metro, New Bern, NC Metro Area, saw a rent burden drop of 1.9 percentage points while maintaining a housing growth score of 34.9.

This demonstrates how steady new construction helps stabilize affordability even as populations rise.

Table: Recommended Congressional Sponsors
Code
# Primary sponsor: among YIMBY successes, pick the one with highest average housing growth
primary <- yimby_summary %>%
  filter(yimby_score == "YIMBY Success") %>%
  arrange(desc(avg_housing_growth)) %>%
  slice(1)

# Co-sponsor: highest recent burden among metros with below-median housing growth
hg_median <- median(yimby_summary$avg_housing_growth, na.rm = TRUE)
cosponsor <- yimby_summary %>%
  filter(avg_housing_growth <= hg_median) %>%
  arrange(desc(recent_burden)) %>%
  slice(1)

primary_name   <- primary$City_Name[1]
cosponsor_name <- cosponsor$City_Name[1]

sponsors <- tibble::tibble(
  Role = c("Primary Sponsor (YIMBY success)", "Co-Sponsor (High burden, low growth)"),
  `Metro Area` = c(primary_name, cosponsor_name),
  `Early Rent Burden %`  = c(round(primary$early_burden[1], 1),  round(cosponsor$early_burden[1], 1)),
  `Recent Rent Burden %` = c(round(primary$recent_burden[1], 1), round(cosponsor$recent_burden[1], 1)),
  `Change in Burden (pp)` = c(round(primary$burden_change[1], 1), round(cosponsor$burden_change[1], 1)),
  `Population Growth %`    = c(round(primary$pop_growth[1], 1),   round(cosponsor$pop_growth[1], 1)),
  `Avg Housing Growth Score` = c(round(primary$avg_housing_growth[1], 1), round(cosponsor$avg_housing_growth[1], 1))
)

DT::datatable(
  sponsors,
  caption = "Recommended Congressional Sponsors",
  options = list(pageLength = 5, searching = FALSE, lengthChange = FALSE),
  rownames = FALSE
)

The table highlights two contrasting metros that capture both success and need in housing reform.

  • The primary sponsor city, New Bern, NC Metro Area, achieved a rent burden decline of 1.9 percentage points and strong housing growth (34.9 score), proving that steady permitting can reduce affordability pressures.
  • The co-sponsor city, Miami-Fort Lauderdale-West Palm Beach, FL Metro Area, saw rent burden rise to 30.1% with limited housing growth (4.4 score), showing the cost of underbuilding.

Together, these cities make a compelling case for federal incentives that expand local housing capacity while maintaining affordability.

8 Policy Brief

8.1 Why this bill is needed

Across U.S. metropolitan areas, rent burdens remain high even as incomes rise.

Our analysis shows that metros which consistently permit new housing experience lower rent burdens without slowing job or population growth.

Federal incentives can help local governments sustain this success by encouraging steady, multi-year housing pipelines rather than short-term construction spikes.

8.2 Understanding the metrics

Two indicators guide where policy can have the greatest impact:

  • Rent Burden (%): Share of a typical household’s income spent on rent
    (Monthly Rent × 12 ÷ Household Income × 100).
    Standardized into a Burden Score (0–100), where higher values signal greater affordability pressure.

  • Housing Growth (Composite Score): Combines two complementary measures:

    1. Production (instantaneous) — housing permits per 1,000 residents, reflecting current construction activity.
    2. Rate-based (5-year responsive) — housing permits relative to population growth, showing whether new supply keeps pace with demand.

Higher scores indicate regions successfully matching construction to population needs.

8.4 Coalition: who benefits from expanding housing

Broad-based support for this bill can come from key industries that directly gain from more stable housing markets:

  • Construction (NAICS 23): Predictable permitting creates steady, skilled jobs and stronger wage security for workers.
  • Health Care & Social Assistance (NAICS 62): Affordable housing allows hospitals and clinics to retain nurses, aides, and frontline staff, strengthening essential community services.
Table: Key Industries in Proposed Sponsor Metros

9 Conclusion

Housing affordability improves when cities build steadily, not sporadically.

This analysis shows that consistent permitting, supported by smart federal incentives, can help every metro area balance growth with livability.

By rewarding results and empowering local governments to build, this bill moves the nation from housing shortage to housing stability.